'use strict';

import * as d3 from 'd3'

function Neo4jD3(_selector, _options) {
  var container, graph, info, node, nodes, relationship, relationshipOutline, relationshipOverlay, relationshipText,
    relationships, selector, simulation, svg, svgNodes, svgRelationships, svgScale, svgTranslate,
    classes2colors = {},
    justLoaded = false,
    numClasses = 0,
    options = {
      arrowSize: 4,
      colors: colors(),
      highlight: undefined,
      iconMap: fontAwesomeIcons(),
      icons: undefined,
      imageMap: {},
      images: undefined,
      infoPanel: true,
      minCollision: undefined,
      neo4jData: undefined,
      neo4jDataUrl: undefined,
      nodeOutlineFillColor: undefined,
      nodeRadius: 25,
      relationshipColor: '#a5abb6',
      zoomFit: false
    },
    VERSION = '0.0.1';

  function appendGraph(container) {
    svg = container.append('svg')
      .attr('width', '100%')
      .attr('height', '100%')
      .attr('class', 'neo4jd3-graph')
      .call(d3.zoom().on('zoom', function () {
        var scale = d3.event.transform.k,
          translate = [d3.event.transform.x, d3.event.transform.y];

        if (svgTranslate) {
          translate[0] += svgTranslate[0];
          translate[1] += svgTranslate[1];
        }

        if (svgScale) {
          scale *= svgScale;
        }

        svg.attr('transform', 'translate(' + translate[0] + ', ' + translate[1] + ') scale(' + scale + ')');
      }))
      .on('dblclick.zoom', null)
      .append('g')
      .attr('width', '100%')
      .attr('height', '100%');

    svgRelationships = svg.append('g')
      .attr('class', 'relationships');

    svgNodes = svg.append('g')
      .attr('class', 'nodes');
  }

  function appendInfoPanel(container) {
    return container.append('div').attr('class', 'neo4jd3-info');
  }

  function appendInfoElement(cls, isNode, property, value) {
    var elem = info.append('a');

    elem.attr('href', '#')
      .attr('class', cls)
      .html('<strong>' + property + '</strong>' + (value ? (': ' + value) : ''));

    if (!value) {
      elem.style('background-color', function (d) {
        return options.nodeOutlineFillColor ? options.nodeOutlineFillColor : (isNode ? class2color(property) : defaultColor());
      })
        .style('border-color', function (d) {
          return options.nodeOutlineFillColor ? class2darkenColor(options.nodeOutlineFillColor) : (isNode ? class2darkenColor(property) : defaultDarkenColor());
        })
        .style('color', function (d) {
          return options.nodeOutlineFillColor ? class2darkenColor(options.nodeOutlineFillColor) : '#fff';
        });
    }
  }

  function appendInfoElementClass(cls, node) {
    appendInfoElement(cls, true, node);
  }

  function appendInfoElementProperty(cls, property, value) {
    appendInfoElement(cls, false, property, value);
  }

  function appendInfoElementRelationship(cls, relationship) {
    appendInfoElement(cls, false, relationship);
  }

  function appendNode() {
    return node.enter()
      .append('g')
      .attr('class', function (d) {
        var highlight, i,
          classes = 'node',
          label = d.label;

        if (icon(d)) {
          classes += ' node-icon';
        }

        if (image(d)) {
          classes += ' node-image';
        }

        if (options.highlight) {
          for (i = 0; i < options.highlight.length; i++) {
            highlight = options.highlight[i];

            if (d.label === highlight.class && d.properties[highlight.property] === highlight.value) {
              classes += ' node-highlighted';
              break;
            }
          }
        }

        return classes;
      })
      .on('click', function (d) {
        d.fx = d.fy = null;
        if (typeof options.onNodeClick === 'function') {
          options.onNodeClick(d);
        }
      })
      .on('dblclick', function (d) {
        if (typeof options.onNodeDoubleClick === 'function') {
          options.onNodeDoubleClick(d);
        }
      })
      .on('mouseenter', function (d) {
        if (info) {
          updateInfo(d);
        }

        if (typeof options.onNodeMouseEnter === 'function') {
          options.onNodeMouseEnter(d);
        }
      })
      .on('mouseleave', function (d) {
        if (info) {
          clearInfo(d);
        }

        if (typeof options.onNodeMouseLeave === 'function') {
          options.onNodeMouseLeave(d);
        }
      })
      .call(d3.drag()
        .on('start', dragStarted)
        .on('drag', dragged)
        .on('end', dragEnded));
  }

  /**
   * 渲染节点到视图
   * @returns {*}
   */
  function appendNodeToGraph() {
    //基础图形
    var n = appendNode();
    appendImageBackground(n);
    // appendRingToNode(n);
    appendOutlineToNode(n);
    appendTextToNode(n);
    return n;
  }

  /**
   * 插入图像的填充对象
   */
  function appendImageBackground(node) {
    node.append('pattern')
      .attr('id', function (d) {
        return 'raduisImage-' + d.id;
      })
      .attr('patternUnits', 'objectBoundingBox')
      .attr('width', '100%')
      .attr('height', '100%')
      .append('image')
      .attr('xlink:href', function (d) {
        return d.properties.avatarUrl;
      })
      .attr('x', 0)
      .attr('y', 0)
      .attr('width', options.nodeRadius * 2 + 'px')
      .attr('height', options.nodeRadius * 2 + 'px')
      .attr("preserveAspectRatio", "none")
    ;
  }

  function appendOutlineToNode(node) {
    return node.append('circle')
      .attr('class', 'outline')
      .attr('r', options.nodeRadius)
      .style('fill', function (d) {
        return d.properties.avatarUrl ? 'url(#raduisImage-' + d.id + ')' : options.nodeOutlineFillColor ? options.nodeOutlineFillColor : class2color(d.label)
      })
      .style('stroke', function (d) {
        return options.nodeOutlineFillColor ? class2darkenColor(options.nodeOutlineFillColor) : class2darkenColor(d.label);
      })
  }

  function appendRingToNode(node) {
    return node.append('circle')
      .attr('class', 'ring')
      .attr('r', options.nodeRadius * 1.16)
      .append('title').text(function (d) {
        return d.name;
      });
  }

  function appendTextToNode(node) {
    return node.append('text')
      .attr('class', 'text')
      .attr('fill', '#2a0808')
      .attr('font-size', '14px')
      .attr('pointer-events', 'none')
      .attr('text-anchor', 'middle')
      .attr('y', function (d) {
        return icon(d) ? (parseInt(Math.round(options.nodeRadius * 0.32)) + 'px') : '4px';
      })
      .html(function (d) {
        return d.properties.name;
      });
  }

  function appendRandomDataToNode(d, maxNodesToGenerate) {
    var data = randomD3Data(d, maxNodesToGenerate);
    updateWithNeo4jData(data);
  }

  function appendRelationship() {
    return relationship.enter()
      .append('g')
      .attr('class', 'relationship')
      .on('dblclick', function (d) {
        if (typeof options.onRelationshipDoubleClick === 'function') {
          options.onRelationshipDoubleClick(d);
        }
      })
      .on('mouseenter', function (d) {
        if (info) {
          updateInfo(d);
        }
      });
  }

  function appendOutlineToRelationship(r) {
    return r.append('path')
      .attr('class', 'outline')
      .attr('fill', '#a5abb6')
      .attr('stroke', 'none')
      ;
  }

  function appendOverlayToRelationship(r) {
    return r.append('path')
      .attr('class', 'overlay');
  }

  function appendTextToRelationship(r) {
    return r.append('text')
      .attr('class', 'text')
      .attr('fill', '#000000')
      .attr('font-size', '14px')
      .attr('pointer-events', 'none')
      .attr('text-anchor', 'middle')
      .text(function (d) {
        return d.type;
      });
  }

  function appendRelationshipToGraph() {
    var relationship = appendRelationship(),
      text = appendTextToRelationship(relationship),
      outline = appendOutlineToRelationship(relationship),
      overlay = appendOverlayToRelationship(relationship);

    return {
      outline: outline,
      overlay: overlay,
      relationship: relationship,
      text: text
    };
  }

  function class2color(cls) {
    var color = classes2colors[cls];

    if (!color) {
//            color = options.colors[Math.min(numClasses, options.colors.length - 1)];
      color = options.colors[numClasses % options.colors.length];
      classes2colors[cls] = color;
      numClasses++;
    }

    return color;
  }

  function class2darkenColor(cls) {
    return d3.rgb(class2color(cls)).darker(1);
  }

  function clearInfo() {
    info.html('');
  }

  function color() {
    return options.colors[options.colors.length * Math.random() << 0];
  }

  function colors() {
    // d3.schemeCategory10,
    // d3.schemeCategory20,
    return [
      '#68bdf6', // light blue
      '#6dce9e', // green #1
      '#faafc2', // light pink
      '#f2baf6', // purple
      '#ff928c', // light red
      '#fcea7e', // light yellow
      '#ffc766', // light orange
      '#405f9e', // navy blue
      '#a5abb6', // dark gray
      '#78cecb', // green #2,
      '#b88cbb', // dark purple
      '#ced2d9', // light gray
      '#e84646', // dark red
      '#fa5f86', // dark pink
      '#ffab1a', // dark orange
      '#fcda19', // dark yellow
      '#797b80', // black
      '#c9d96f', // pistacchio
      '#47991f', // green #3
      '#70edee', // turquoise
      '#ff75ea'  // pink
    ];
  }

  function contains(array, id) {
    var filter = array.filter(function (elem) {
      return elem.id === id;
    });

    return filter.length > 0;
  }

  function defaultColor() {
    return options.relationshipColor;
  }

  function defaultDarkenColor() {
    return d3.rgb(options.colors[options.colors.length - 1]).darker(1);
  }

  function dragEnded(d) {
    if (!d3.event.active) {
      simulation.alphaTarget(0);
    }

    if (typeof options.onNodeDragEnd === 'function') {
      options.onNodeDragEnd(d);
    }
  }

  function dragged(d) {
    stickNode(d);
  }

  function dragStarted(d) {
    if (!d3.event.active) {
      simulation.alphaTarget(0.3).restart();
    }

    d.fx = d.x;
    d.fy = d.y;

    if (typeof options.onNodeDragStart === 'function') {
      options.onNodeDragStart(d);
    }
  }

  function extend(obj1, obj2) {
    var obj = {};

    merge(obj, obj1);
    merge(obj, obj2);

    return obj;
  }

  function fontAwesomeIcons() {
    return {
      'glass': 'f000',
      'music': 'f001',
      'search': 'f002',
      'envelope-o': 'f003',
      'heart': 'f004',
      'star': 'f005',
      'star-o': 'f006',
      'user': 'f007',
      'film': 'f008',
      'th-large': 'f009',
      'th': 'f00a',
      'th-list': 'f00b',
      'check': 'f00c',
      'remove,close,times': 'f00d',
      'search-plus': 'f00e',
      'search-minus': 'f010',
      'power-off': 'f011',
      'signal': 'f012',
      'gear,cog': 'f013',
      'trash-o': 'f014',
      'home': 'f015',
      'file-o': 'f016',
      'clock-o': 'f017',
      'road': 'f018',
      'download': 'f019',
      'arrow-circle-o-down': 'f01a',
      'arrow-circle-o-up': 'f01b',
      'inbox': 'f01c',
      'play-circle-o': 'f01d',
      'rotate-right,repeat': 'f01e',
      'refresh': 'f021',
      'list-alt': 'f022',
      'lock': 'f023',
      'flag': 'f024',
      'headphones': 'f025',
      'volume-off': 'f026',
      'volume-down': 'f027',
      'volume-up': 'f028',
      'qrcode': 'f029',
      'barcode': 'f02a',
      'tag': 'f02b',
      'tags': 'f02c',
      'book': 'f02d',
      'bookmark': 'f02e',
      'print': 'f02f',
      'camera': 'f030',
      'font': 'f031',
      'bold': 'f032',
      'italic': 'f033',
      'text-height': 'f034',
      'text-width': 'f035',
      'align-left': 'f036',
      'align-center': 'f037',
      'align-right': 'f038',
      'align-justify': 'f039',
      'list': 'f03a',
      'dedent,outdent': 'f03b',
      'indent': 'f03c',
      'video-camera': 'f03d',
      'photo,image,picture-o': 'f03e',
      'pencil': 'f040',
      'map-marker': 'f041',
      'adjust': 'f042',
      'tint': 'f043',
      'edit,pencil-square-o': 'f044',
      'share-square-o': 'f045',
      'check-square-o': 'f046',
      'arrows': 'f047',
      'step-backward': 'f048',
      'fast-backward': 'f049',
      'backward': 'f04a',
      'play': 'f04b',
      'pause': 'f04c',
      'stop': 'f04d',
      'forward': 'f04e',
      'fast-forward': 'f050',
      'step-forward': 'f051',
      'eject': 'f052',
      'chevron-left': 'f053',
      'chevron-right': 'f054',
      'plus-circle': 'f055',
      'minus-circle': 'f056',
      'times-circle': 'f057',
      'check-circle': 'f058',
      'question-circle': 'f059',
      'info-circle': 'f05a',
      'crosshairs': 'f05b',
      'times-circle-o': 'f05c',
      'check-circle-o': 'f05d',
      'ban': 'f05e',
      'arrow-left': 'f060',
      'arrow-right': 'f061',
      'arrow-up': 'f062',
      'arrow-down': 'f063',
      'mail-forward,share': 'f064',
      'expand': 'f065',
      'compress': 'f066',
      'plus': 'f067',
      'minus': 'f068',
      'asterisk': 'f069',
      'exclamation-circle': 'f06a',
      'gift': 'f06b',
      'leaf': 'f06c',
      'fire': 'f06d',
      'eye': 'f06e',
      'eye-slash': 'f070',
      'warning,exclamation-triangle': 'f071',
      'plane': 'f072',
      'calendar': 'f073',
      'random': 'f074',
      'comment': 'f075',
      'magnet': 'f076',
      'chevron-up': 'f077',
      'chevron-down': 'f078',
      'retweet': 'f079',
      'shopping-cart': 'f07a',
      'folder': 'f07b',
      'folder-open': 'f07c',
      'arrows-v': 'f07d',
      'arrows-h': 'f07e',
      'bar-chart-o,bar-chart': 'f080',
      'twitter-square': 'f081',
      'facebook-square': 'f082',
      'camera-retro': 'f083',
      'key': 'f084',
      'gears,cogs': 'f085',
      'comments': 'f086',
      'thumbs-o-up': 'f087',
      'thumbs-o-down': 'f088',
      'star-half': 'f089',
      'heart-o': 'f08a',
      'sign-out': 'f08b',
      'linkedin-square': 'f08c',
      'thumb-tack': 'f08d',
      'external-link': 'f08e',
      'sign-in': 'f090',
      'trophy': 'f091',
      'github-square': 'f092',
      'upload': 'f093',
      'lemon-o': 'f094',
      'phone': 'f095',
      'square-o': 'f096',
      'bookmark-o': 'f097',
      'phone-square': 'f098',
      'twitter': 'f099',
      'facebook-f,facebook': 'f09a',
      'github': 'f09b',
      'unlock': 'f09c',
      'credit-card': 'f09d',
      'feed,rss': 'f09e',
      'hdd-o': 'f0a0',
      'bullhorn': 'f0a1',
      'bell': 'f0f3',
      'certificate': 'f0a3',
      'hand-o-right': 'f0a4',
      'hand-o-left': 'f0a5',
      'hand-o-up': 'f0a6',
      'hand-o-down': 'f0a7',
      'arrow-circle-left': 'f0a8',
      'arrow-circle-right': 'f0a9',
      'arrow-circle-up': 'f0aa',
      'arrow-circle-down': 'f0ab',
      'globe': 'f0ac',
      'wrench': 'f0ad',
      'tasks': 'f0ae',
      'filter': 'f0b0',
      'briefcase': 'f0b1',
      'arrows-alt': 'f0b2',
      'group,users': 'f0c0',
      'chain,link': 'f0c1',
      'cloud': 'f0c2',
      'flask': 'f0c3',
      'cut,scissors': 'f0c4',
      'copy,files-o': 'f0c5',
      'paperclip': 'f0c6',
      'save,floppy-o': 'f0c7',
      'square': 'f0c8',
      'navicon,reorder,bars': 'f0c9',
      'list-ul': 'f0ca',
      'list-ol': 'f0cb',
      'strikethrough': 'f0cc',
      'underline': 'f0cd',
      'table': 'f0ce',
      'magic': 'f0d0',
      'truck': 'f0d1',
      'pinterest': 'f0d2',
      'pinterest-square': 'f0d3',
      'google-plus-square': 'f0d4',
      'google-plus': 'f0d5',
      'money': 'f0d6',
      'caret-down': 'f0d7',
      'caret-up': 'f0d8',
      'caret-left': 'f0d9',
      'caret-right': 'f0da',
      'columns': 'f0db',
      'unsorted,sort': 'f0dc',
      'sort-down,sort-desc': 'f0dd',
      'sort-up,sort-asc': 'f0de',
      'envelope': 'f0e0',
      'linkedin': 'f0e1',
      'rotate-left,undo': 'f0e2',
      'legal,gavel': 'f0e3',
      'dashboard,tachometer': 'f0e4',
      'comment-o': 'f0e5',
      'comments-o': 'f0e6',
      'flash,bolt': 'f0e7',
      'sitemap': 'f0e8',
      'umbrella': 'f0e9',
      'paste,clipboard': 'f0ea',
      'lightbulb-o': 'f0eb',
      'exchange': 'f0ec',
      'cloud-download': 'f0ed',
      'cloud-upload': 'f0ee',
      'user-md': 'f0f0',
      'stethoscope': 'f0f1',
      'suitcase': 'f0f2',
      'bell-o': 'f0a2',
      'coffee': 'f0f4',
      'cutlery': 'f0f5',
      'file-text-o': 'f0f6',
      'building-o': 'f0f7',
      'hospital-o': 'f0f8',
      'ambulance': 'f0f9',
      'medkit': 'f0fa',
      'fighter-jet': 'f0fb',
      'beer': 'f0fc',
      'h-square': 'f0fd',
      'plus-square': 'f0fe',
      'angle-double-left': 'f100',
      'angle-double-right': 'f101',
      'angle-double-up': 'f102',
      'angle-double-down': 'f103',
      'angle-left': 'f104',
      'angle-right': 'f105',
      'angle-up': 'f106',
      'angle-down': 'f107',
      'desktop': 'f108',
      'laptop': 'f109',
      'tablet': 'f10a',
      'mobile-phone,mobile': 'f10b',
      'circle-o': 'f10c',
      'quote-left': 'f10d',
      'quote-right': 'f10e',
      'spinner': 'f110',
      'circle': 'f111',
      'mail-reply,reply': 'f112',
      'github-alt': 'f113',
      'folder-o': 'f114',
      'folder-open-o': 'f115',
      'smile-o': 'f118',
      'frown-o': 'f119',
      'meh-o': 'f11a',
      'gamepad': 'f11b',
      'keyboard-o': 'f11c',
      'flag-o': 'f11d',
      'flag-checkered': 'f11e',
      'terminal': 'f120',
      'code': 'f121',
      'mail-reply-all,reply-all': 'f122',
      'star-half-empty,star-half-full,star-half-o': 'f123',
      'location-arrow': 'f124',
      'crop': 'f125',
      'code-fork': 'f126',
      'unlink,chain-broken': 'f127',
      'question': 'f128',
      'info': 'f129',
      'exclamation': 'f12a',
      'superscript': 'f12b',
      'subscript': 'f12c',
      'eraser': 'f12d',
      'puzzle-piece': 'f12e',
      'microphone': 'f130',
      'microphone-slash': 'f131',
      'shield': 'f132',
      'calendar-o': 'f133',
      'fire-extinguisher': 'f134',
      'rocket': 'f135',
      'maxcdn': 'f136',
      'chevron-circle-left': 'f137',
      'chevron-circle-right': 'f138',
      'chevron-circle-up': 'f139',
      'chevron-circle-down': 'f13a',
      'html5': 'f13b',
      'css3': 'f13c',
      'anchor': 'f13d',
      'unlock-alt': 'f13e',
      'bullseye': 'f140',
      'ellipsis-h': 'f141',
      'ellipsis-v': 'f142',
      'rss-square': 'f143',
      'play-circle': 'f144',
      'ticket': 'f145',
      'minus-square': 'f146',
      'minus-square-o': 'f147',
      'level-up': 'f148',
      'level-down': 'f149',
      'check-square': 'f14a',
      'pencil-square': 'f14b',
      'external-link-square': 'f14c',
      'share-square': 'f14d',
      'compass': 'f14e',
      'toggle-down,caret-square-o-down': 'f150',
      'toggle-up,caret-square-o-up': 'f151',
      'toggle-right,caret-square-o-right': 'f152',
      'euro,eur': 'f153',
      'gbp': 'f154',
      'dollar,usd': 'f155',
      'rupee,inr': 'f156',
      'cny,rmb,yen,jpy': 'f157',
      'ruble,rouble,rub': 'f158',
      'won,krw': 'f159',
      'bitcoin,btc': 'f15a',
      'file': 'f15b',
      'file-text': 'f15c',
      'sort-alpha-asc': 'f15d',
      'sort-alpha-desc': 'f15e',
      'sort-amount-asc': 'f160',
      'sort-amount-desc': 'f161',
      'sort-numeric-asc': 'f162',
      'sort-numeric-desc': 'f163',
      'thumbs-up': 'f164',
      'thumbs-down': 'f165',
      'youtube-square': 'f166',
      'youtube': 'f167',
      'xing': 'f168',
      'xing-square': 'f169',
      'youtube-play': 'f16a',
      'dropbox': 'f16b',
      'stack-overflow': 'f16c',
      'instagram': 'f16d',
      'flickr': 'f16e',
      'adn': 'f170',
      'bitbucket': 'f171',
      'bitbucket-square': 'f172',
      'tumblr': 'f173',
      'tumblr-square': 'f174',
      'long-arrow-down': 'f175',
      'long-arrow-up': 'f176',
      'long-arrow-left': 'f177',
      'long-arrow-right': 'f178',
      'apple': 'f179',
      'windows': 'f17a',
      'android': 'f17b',
      'linux': 'f17c',
      'dribbble': 'f17d',
      'skype': 'f17e',
      'foursquare': 'f180',
      'trello': 'f181',
      'female': 'f182',
      'male': 'f183',
      'gittip,gratipay': 'f184',
      'sun-o': 'f185',
      'moon-o': 'f186',
      'archive': 'f187',
      'bug': 'f188',
      'vk': 'f189',
      'weibo': 'f18a',
      'renren': 'f18b',
      'pagelines': 'f18c',
      'stack-exchange': 'f18d',
      'arrow-circle-o-right': 'f18e',
      'arrow-circle-o-left': 'f190',
      'toggle-left,caret-square-o-left': 'f191',
      'dot-circle-o': 'f192',
      'wheelchair': 'f193',
      'vimeo-square': 'f194',
      'turkish-lira,try': 'f195',
      'plus-square-o': 'f196',
      'space-shuttle': 'f197',
      'slack': 'f198',
      'envelope-square': 'f199',
      'wordpress': 'f19a',
      'openid': 'f19b',
      'institution,bank,university': 'f19c',
      'mortar-board,graduation-cap': 'f19d',
      'yahoo': 'f19e',
      'google': 'f1a0',
      'reddit': 'f1a1',
      'reddit-square': 'f1a2',
      'stumbleupon-circle': 'f1a3',
      'stumbleupon': 'f1a4',
      'delicious': 'f1a5',
      'digg': 'f1a6',
      'pied-piper-pp': 'f1a7',
      'pied-piper-alt': 'f1a8',
      'drupal': 'f1a9',
      'joomla': 'f1aa',
      'language': 'f1ab',
      'fax': 'f1ac',
      'building': 'f1ad',
      'child': 'f1ae',
      'paw': 'f1b0',
      'spoon': 'f1b1',
      'cube': 'f1b2',
      'cubes': 'f1b3',
      'behance': 'f1b4',
      'behance-square': 'f1b5',
      'steam': 'f1b6',
      'steam-square': 'f1b7',
      'recycle': 'f1b8',
      'automobile,car': 'f1b9',
      'cab,taxi': 'f1ba',
      'tree': 'f1bb',
      'spotify': 'f1bc',
      'deviantart': 'f1bd',
      'soundcloud': 'f1be',
      'database': 'f1c0',
      'file-pdf-o': 'f1c1',
      'file-word-o': 'f1c2',
      'file-excel-o': 'f1c3',
      'file-powerpoint-o': 'f1c4',
      'file-photo-o,file-picture-o,file-image-o': 'f1c5',
      'file-zip-o,file-archive-o': 'f1c6',
      'file-sound-o,file-audio-o': 'f1c7',
      'file-movie-o,file-video-o': 'f1c8',
      'file-code-o': 'f1c9',
      'vine': 'f1ca',
      'codepen': 'f1cb',
      'jsfiddle': 'f1cc',
      'life-bouy,life-buoy,life-saver,support,life-ring': 'f1cd',
      'circle-o-notch': 'f1ce',
      'ra,resistance,rebel': 'f1d0',
      'ge,empire': 'f1d1',
      'git-square': 'f1d2',
      'git': 'f1d3',
      'y-combinator-square,yc-square,hacker-news': 'f1d4',
      'tencent-weibo': 'f1d5',
      'qq': 'f1d6',
      'wechat,weixin': 'f1d7',
      'send,paper-plane': 'f1d8',
      'send-o,paper-plane-o': 'f1d9',
      'history': 'f1da',
      'circle-thin': 'f1db',
      'header': 'f1dc',
      'paragraph': 'f1dd',
      'sliders': 'f1de',
      'share-alt': 'f1e0',
      'share-alt-square': 'f1e1',
      'bomb': 'f1e2',
      'soccer-ball-o,futbol-o': 'f1e3',
      'tty': 'f1e4',
      'binoculars': 'f1e5',
      'plug': 'f1e6',
      'slideshare': 'f1e7',
      'twitch': 'f1e8',
      'yelp': 'f1e9',
      'newspaper-o': 'f1ea',
      'wifi': 'f1eb',
      'calculator': 'f1ec',
      'paypal': 'f1ed',
      'google-wallet': 'f1ee',
      'cc-visa': 'f1f0',
      'cc-mastercard': 'f1f1',
      'cc-discover': 'f1f2',
      'cc-amex': 'f1f3',
      'cc-paypal': 'f1f4',
      'cc-stripe': 'f1f5',
      'bell-slash': 'f1f6',
      'bell-slash-o': 'f1f7',
      'trash': 'f1f8',
      'copyright': 'f1f9',
      'at': 'f1fa',
      'eyedropper': 'f1fb',
      'paint-brush': 'f1fc',
      'birthday-cake': 'f1fd',
      'area-chart': 'f1fe',
      'pie-chart': 'f200',
      'line-chart': 'f201',
      'lastfm': 'f202',
      'lastfm-square': 'f203',
      'toggle-off': 'f204',
      'toggle-on': 'f205',
      'bicycle': 'f206',
      'bus': 'f207',
      'ioxhost': 'f208',
      'angellist': 'f209',
      'cc': 'f20a',
      'shekel,sheqel,ils': 'f20b',
      'meanpath': 'f20c',
      'buysellads': 'f20d',
      'connectdevelop': 'f20e',
      'dashcube': 'f210',
      'forumbee': 'f211',
      'leanpub': 'f212',
      'sellsy': 'f213',
      'shirtsinbulk': 'f214',
      'simplybuilt': 'f215',
      'skyatlas': 'f216',
      'cart-plus': 'f217',
      'cart-arrow-down': 'f218',
      'diamond': 'f219',
      'ship': 'f21a',
      'user-secret': 'f21b',
      'motorcycle': 'f21c',
      'street-view': 'f21d',
      'heartbeat': 'f21e',
      'venus': 'f221',
      'mars': 'f222',
      'mercury': 'f223',
      'intersex,transgender': 'f224',
      'transgender-alt': 'f225',
      'venus-double': 'f226',
      'mars-double': 'f227',
      'venus-mars': 'f228',
      'mars-stroke': 'f229',
      'mars-stroke-v': 'f22a',
      'mars-stroke-h': 'f22b',
      'neuter': 'f22c',
      'genderless': 'f22d',
      'facebook-official': 'f230',
      'pinterest-p': 'f231',
      'whatsapp': 'f232',
      'server': 'f233',
      'user-plus': 'f234',
      'user-times': 'f235',
      'hotel,bed': 'f236',
      'viacoin': 'f237',
      'train': 'f238',
      'subway': 'f239',
      'medium': 'f23a',
      'yc,y-combinator': 'f23b',
      'optin-monster': 'f23c',
      'opencart': 'f23d',
      'expeditedssl': 'f23e',
      'battery-4,battery-full': 'f240',
      'battery-3,battery-three-quarters': 'f241',
      'battery-2,battery-half': 'f242',
      'battery-1,battery-quarter': 'f243',
      'battery-0,battery-empty': 'f244',
      'mouse-pointer': 'f245',
      'i-cursor': 'f246',
      'object-group': 'f247',
      'object-ungroup': 'f248',
      'sticky-note': 'f249',
      'sticky-note-o': 'f24a',
      'cc-jcb': 'f24b',
      'cc-diners-club': 'f24c',
      'clone': 'f24d',
      'balance-scale': 'f24e',
      'hourglass-o': 'f250',
      'hourglass-1,hourglass-start': 'f251',
      'hourglass-2,hourglass-half': 'f252',
      'hourglass-3,hourglass-end': 'f253',
      'hourglass': 'f254',
      'hand-grab-o,hand-rock-o': 'f255',
      'hand-stop-o,hand-paper-o': 'f256',
      'hand-scissors-o': 'f257',
      'hand-lizard-o': 'f258',
      'hand-spock-o': 'f259',
      'hand-pointer-o': 'f25a',
      'hand-peace-o': 'f25b',
      'trademark': 'f25c',
      'registered': 'f25d',
      'creative-commons': 'f25e',
      'gg': 'f260',
      'gg-circle': 'f261',
      'tripadvisor': 'f262',
      'odnoklassniki': 'f263',
      'odnoklassniki-square': 'f264',
      'get-pocket': 'f265',
      'wikipedia-w': 'f266',
      'safari': 'f267',
      'chrome': 'f268',
      'firefox': 'f269',
      'opera': 'f26a',
      'internet-explorer': 'f26b',
      'tv,television': 'f26c',
      'contao': 'f26d',
      '500px': 'f26e',
      'amazon': 'f270',
      'calendar-plus-o': 'f271',
      'calendar-minus-o': 'f272',
      'calendar-times-o': 'f273',
      'calendar-check-o': 'f274',
      'industry': 'f275',
      'map-pin': 'f276',
      'map-signs': 'f277',
      'map-o': 'f278',
      'map': 'f279',
      'commenting': 'f27a',
      'commenting-o': 'f27b',
      'houzz': 'f27c',
      'vimeo': 'f27d',
      'black-tie': 'f27e',
      'fonticons': 'f280',
      'reddit-alien': 'f281',
      'edge': 'f282',
      'credit-card-alt': 'f283',
      'codiepie': 'f284',
      'modx': 'f285',
      'fort-awesome': 'f286',
      'usb': 'f287',
      'product-hunt': 'f288',
      'mixcloud': 'f289',
      'scribd': 'f28a',
      'pause-circle': 'f28b',
      'pause-circle-o': 'f28c',
      'stop-circle': 'f28d',
      'stop-circle-o': 'f28e',
      'shopping-bag': 'f290',
      'shopping-basket': 'f291',
      'hashtag': 'f292',
      'bluetooth': 'f293',
      'bluetooth-b': 'f294',
      'percent': 'f295',
      'gitlab': 'f296',
      'wpbeginner': 'f297',
      'wpforms': 'f298',
      'envira': 'f299',
      'universal-access': 'f29a',
      'wheelchair-alt': 'f29b',
      'question-circle-o': 'f29c',
      'blind': 'f29d',
      'audio-description': 'f29e',
      'volume-control-phone': 'f2a0',
      'braille': 'f2a1',
      'assistive-listening-systems': 'f2a2',
      'asl-interpreting,american-sign-language-interpreting': 'f2a3',
      'deafness,hard-of-hearing,deaf': 'f2a4',
      'glide': 'f2a5',
      'glide-g': 'f2a6',
      'signing,sign-language': 'f2a7',
      'low-vision': 'f2a8',
      'viadeo': 'f2a9',
      'viadeo-square': 'f2aa',
      'snapchat': 'f2ab',
      'snapchat-ghost': 'f2ac',
      'snapchat-square': 'f2ad',
      'pied-piper': 'f2ae',
      'first-order': 'f2b0',
      'yoast': 'f2b1',
      'themeisle': 'f2b2',
      'google-plus-circle,google-plus-official': 'f2b3',
      'fa,font-awesome': 'f2b4'
    };
  }

  function icon(d) {
    var code;

    if (options.iconMap && options.showIcons && options.icons) {
      if (options.icons[d.label] && options.iconMap[options.icons[d.label]]) {
        code = options.iconMap[options.icons[d.label]];
      } else if (options.iconMap[d.label]) {
        code = options.iconMap[d.label];
      } else if (options.icons[d.label]) {
        code = options.icons[d.label];
      }
    }

    return code;
  }

  function image(d) {
    var i, imagesForLabel, img, imgLevel, label, labelPropertyValue, property, value;

    if (d.properties.avatarUrl) {
      img = d.properties.avatarUrl;
      imgLevel = 1;
    }

    return img;
  }

  function init(_selector, _options) {
    initIconMap();

    merge(options, _options);

    if (options.icons) {
      options.showIcons = true;
    }

    if (!options.minCollision) {
      options.minCollision = options.nodeRadius * 2;
    }

    initImageMap();

    selector = _selector;

    container = d3.select(selector);

    container.attr('class', 'neo4jd3')
      .html('');

    if (options.infoPanel) {
      info = appendInfoPanel(container);
    }

    appendGraph(container);

    simulation = initSimulation();

    if (options.neo4jData) {
      loadNeo4jData(options.neo4jData);
    } else if (options.neo4jDataUrl) {
      loadNeo4jDataFromUrl(options.neo4jDataUrl);
    } else {
      console.error('Error: both neo4jData and neo4jDataUrl are empty!');
    }
  }

  function initIconMap() {
    Object.keys(options.iconMap).forEach(function (key, index) {
      var keys = key.split(','),
        value = options.iconMap[key];

      keys.forEach(function (key) {
        options.iconMap[key] = value;
      });
    });
  }

  function initImageMap() {
    var key, keys, selector;

    for (key in options.images) {
      if (options.images.hasOwnProperty(key)) {
        keys = key.split('|');

        if (!options.imageMap[keys[0]]) {
          options.imageMap[keys[0]] = [key];
        } else {
          options.imageMap[keys[0]].push(key);
        }
      }
    }
  }

  function initSimulation() {

    var simulation = d3.forceSimulation()
      // .velocityDecay(0.8)
      // .force('x', d3.force().strength(0.002))
      // .force('y', d3.force().strength(0.002))
      .force('collide', d3.forceCollide().radius(function (d) {
        return options.minCollision;
      }).iterations(2))
      .force('charge', d3.forceManyBody())
      .force('link', d3.forceLink().id(function (d) {
        return d.id;
      }))
      .force('center', d3.forceCenter(svg.node().parentElement.parentElement.clientWidth / 2, svg.node().parentElement.parentElement.clientHeight / 2))
      .on('tick', function () {
        tick();
      })
      .on('end', function () {
        if (options.zoomFit && !justLoaded) {
          justLoaded = true;
          zoomFit(2);
        }
      });

    return simulation;
  }

  function loadNeo4jData() {
    nodes = [];
    relationships = [];

    updateWithNeo4jData(options.neo4jData);
  }

  function loadNeo4jDataFromUrl(neo4jDataUrl) {
    nodes = [];
    relationships = [];

    d3.json(neo4jDataUrl, function (error, data) {
      if (error) {
        throw error;
      }

      updateWithNeo4jData(data);
    });
  }

  function merge(target, source) {
    Object.keys(source).forEach(function (property) {
      target[property] = source[property];
    });
  }

  /**
   * JSON数据转换为D3所需要的数据
   * @param data
   * @returns {{relationships: *[], nodes: *[]}}
   */
  function neo4jDataToD3Data(data) {
    var graph = {
      nodes: [],
      relationships: []
    };

    data.nodes.forEach(function (node) {
      if (!contains(graph.nodes, node.id)) {
        graph.nodes.push(node);
      }
    });

    data.relationships.forEach(function (relationship) {
      relationship.source = relationship.startNode;
      relationship.target = relationship.endNode;
      graph.relationships.push(relationship);
    });

    //关系排序
    data.relationships.sort(function (a, b) {
      if (a.source > b.source) {
        return 1;
      } else if (a.source < b.source) {
        return -1;
      } else {
        if (a.target > b.target) {
          return 1;
        }

        if (a.target < b.target) {
          return -1;
        } else {
          return 0;
        }
      }
    });

    for (var i = 0; i < data.relationships.length; i++) {
      if (i !== 0 && data.relationships[i].source === data.relationships[i - 1].source && data.relationships[i].target === data.relationships[i - 1].target) {
        data.relationships[i].linknum = data.relationships[i - 1].linknum + 1;
      } else {
        data.relationships[i].linknum = 1;
      }
    }

    return graph;
  }

  function rotate(cx, cy, x, y, angle) {
    var radians = (Math.PI / 180) * angle,
      cos = Math.cos(radians),
      sin = Math.sin(radians),
      nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
      ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;

    return {x: nx, y: ny};
  }

  function rotatePoint(c, p, angle) {
    return rotate(c.x, c.y, p.x, p.y, angle);
  }

  function rotation(source, target) {
    return Math.atan2(target.y - source.y, target.x - source.x) * 180 / Math.PI;
  }

  function size() {
    return {
      nodes: nodes.length,
      relationships: relationships.length
    };
  }

  /*
      function smoothTransform(elem, translate, scale) {
          var animationMilliseconds = 5000,
              timeoutMilliseconds = 50,
              steps = parseInt(animationMilliseconds / timeoutMilliseconds);

          setTimeout(function() {
              smoothTransformStep(elem, translate, scale, timeoutMilliseconds, 1, steps);
          }, timeoutMilliseconds);
      }

      function smoothTransformStep(elem, translate, scale, timeoutMilliseconds, step, steps) {
          var progress = step / steps;

          elem.attr('transform', 'translate(' + (translate[0] * progress) + ', ' + (translate[1] * progress) + ') scale(' + (scale * progress) + ')');

          if (step < steps) {
              setTimeout(function() {
                  smoothTransformStep(elem, translate, scale, timeoutMilliseconds, step + 1, steps);
              }, timeoutMilliseconds);
          }
      }
  */
  function stickNode(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }

  function tick() {
    tickNodes();
    tickRelationships();
  }

  function tickNodes() {
    if (node) {
      node.attr('transform', function (d) {
        return 'translate(' + d.x + ', ' + d.y + ')';
      });
    }
  }

  function tickRelationships() {
    if (relationship) {
      relationship.attr('transform', function (d) {
        var angle = rotation(d.source, d.target);
        return 'translate(' + d.source.x + ', ' + d.source.y + ') rotate(' + angle + ')';
      });

      tickRelationshipsTexts();
      tickRelationshipsOutlines();
      tickRelationshipsOverlays();
    }
  }

  function tickRelationshipsOutlines() {
    relationship.each(function (relationship) {
      var rel = d3.select(this),
        outline = rel.select('.outline'),
        text = rel.select('.text'),
        bbox = text.node().getBBox(),
        padding = 3;

      outline.attr('d', function (d) {
        var center = {x: 0, y: 0},
          angle = rotation(d.source, d.target),
          textBoundingBox = text.node().getBBox(),
          textPadding = 5,
          u = unitaryVector(d.source, d.target),
          textMargin = {
            x: (d.target.x - d.source.x - (textBoundingBox.width + textPadding) * u.x) * 0.5,
            y: (d.target.y - d.source.y - (textBoundingBox.width + textPadding) * u.y) * 0.5
          },
          n = unitaryNormalVector(d.source, d.target),
          rotatedPointA1 = rotatePoint(center, {
            x: 0 + (options.nodeRadius + 1) * u.x - n.x,
            y: 0 + (options.nodeRadius + 1) * u.y - n.y
          }, angle),
          rotatedPointB1 = rotatePoint(center, {x: textMargin.x - n.x, y: textMargin.y - n.y}, angle),
          rotatedPointC1 = rotatePoint(center, {x: textMargin.x, y: textMargin.y}, angle),
          rotatedPointD1 = rotatePoint(center, {
            x: 0 + (options.nodeRadius + 1) * u.x,
            y: 0 + (options.nodeRadius + 1) * u.y
          }, angle),
          rotatedPointA2 = rotatePoint(center, {
            x: d.target.x - d.source.x - textMargin.x - n.x,
            y: d.target.y - d.source.y - textMargin.y - n.y
          }, angle),
          rotatedPointB2 = rotatePoint(center, {
            x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x - n.x - u.x * options.arrowSize,
            y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y - n.y - u.y * options.arrowSize
          }, angle),
          rotatedPointC2 = rotatePoint(center, {
            x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x - n.x + (n.x - u.x) * options.arrowSize,
            y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y - n.y + (n.y - u.y) * options.arrowSize
          }, angle),
          rotatedPointD2 = rotatePoint(center, {
            x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x,
            y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y
          }, angle),
          rotatedPointE2 = rotatePoint(center, {
            x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x + (-n.x - u.x) * options.arrowSize,
            y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y + (-n.y - u.y) * options.arrowSize
          }, angle),
          rotatedPointF2 = rotatePoint(center, {
            x: d.target.x - d.source.x - (options.nodeRadius + 1) * u.x - u.x * options.arrowSize,
            y: d.target.y - d.source.y - (options.nodeRadius + 1) * u.y - u.y * options.arrowSize
          }, angle),
          rotatedPointG2 = rotatePoint(center, {
            x: d.target.x - d.source.x - textMargin.x,
            y: d.target.y - d.source.y - textMargin.y
          }, angle);

        return 'M ' + rotatedPointA1.x + ' ' + rotatedPointA1.y +
          ' L ' + rotatedPointB1.x + ' ' + rotatedPointB1.y +
          ' L ' + rotatedPointC1.x + ' ' + rotatedPointC1.y +
          ' L ' + rotatedPointD1.x + ' ' + rotatedPointD1.y +
          ' Z M ' + rotatedPointA2.x + ' ' + rotatedPointA2.y +
          ' L ' + rotatedPointB2.x + ' ' + rotatedPointB2.y +
          ' L ' + rotatedPointC2.x + ' ' + rotatedPointC2.y +
          ' L ' + rotatedPointD2.x + ' ' + rotatedPointD2.y +
          ' L ' + rotatedPointE2.x + ' ' + rotatedPointE2.y +
          ' L ' + rotatedPointF2.x + ' ' + rotatedPointF2.y +
          ' L ' + rotatedPointG2.x + ' ' + rotatedPointG2.y +
          ' Z';
      });
    });
  }

  function tickRelationshipsOverlays() {
    relationshipOverlay.attr('d', function (d) {
      var center = {x: 0, y: 0},
        angle = rotation(d.source, d.target),
        n1 = unitaryNormalVector(d.source, d.target),
        n = unitaryNormalVector(d.source, d.target, 50),
        rotatedPointA = rotatePoint(center, {x: 0 - n.x, y: 0 - n.y}, angle),
        rotatedPointB = rotatePoint(center, {
          x: d.target.x - d.source.x - n.x,
          y: d.target.y - d.source.y - n.y
        }, angle),
        rotatedPointC = rotatePoint(center, {
          x: d.target.x - d.source.x + n.x - n1.x,
          y: d.target.y - d.source.y + n.y - n1.y
        }, angle),
        rotatedPointD = rotatePoint(center, {x: 0 + n.x - n1.x, y: 0 + n.y - n1.y}, angle);

      return 'M ' + rotatedPointA.x + ' ' + rotatedPointA.y +
        ' L ' + rotatedPointB.x + ' ' + rotatedPointB.y +
        ' L ' + rotatedPointC.x + ' ' + rotatedPointC.y +
        ' L ' + rotatedPointD.x + ' ' + rotatedPointD.y +
        ' Z';
    });
  }

  function tickRelationshipsTexts() {
    relationshipText.attr('transform', function (d) {
      var angle = (rotation(d.source, d.target) + 360) % 360,
        mirror = angle > 90 && angle < 270,
        center = {x: 0, y: 0},
        n = unitaryNormalVector(d.source, d.target),
        nWeight = mirror ? 2 : -3,
        point = {
          x: (d.target.x - d.source.x) * 0.5 + n.x * nWeight,
          y: (d.target.y - d.source.y) * 0.5 + n.y * nWeight
        },
        rotatedPoint = rotatePoint(center, point, angle);

      return 'translate(' + rotatedPoint.x + ', ' + rotatedPoint.y + ') rotate(' + (mirror ? 180 : 0) + ')';
    });
  }

  function toString(d) {
    var s = d.label ? d.label : d.type;

    s += ' (<id>: ' + d.id;

    Object.keys(d.properties).forEach(function (property) {
      s += ', ' + property + ': ' + JSON.stringify(d.properties[property]);
    });

    s += ')';

    return s;
  }

  function unitaryNormalVector(source, target, newLength) {
    var center = {x: 0, y: 0},
      vector = unitaryVector(source, target, newLength);

    return rotatePoint(center, vector, 90);
  }

  function unitaryVector(source, target, newLength) {
    var length = Math.sqrt(Math.pow(target.x - source.x, 2) + Math.pow(target.y - source.y, 2)) / Math.sqrt(newLength || 1);

    return {
      x: (target.x - source.x) / length,
      y: (target.y - source.y) / length,
    };
  }

  function updateWithD3Data(d3Data) {
    updateNodesAndRelationships(d3Data.nodes, d3Data.relationships);
  }

  /**
   * 初始化Neo4j的数据
   * @param neo4jData
   */
  function updateWithNeo4jData(neo4jData) {
    var d3Data = neo4jDataToD3Data(neo4jData);
    updateWithD3Data(d3Data);
  }

  function updateInfo(d) {
    clearInfo();

    if (d.label) {
      appendInfoElementClass('class', d.label);
    } else {
      appendInfoElementRelationship('class', d.type);
    }

    appendInfoElementProperty('property', '&lt;id&gt;', d.id);

    Object.keys(d.properties).forEach(function (property) {
      appendInfoElementProperty('property', property, JSON.stringify(d.properties[property]));
    });
  }

  function updateNodes(n) {
    Array.prototype.push.apply(nodes, n);
    let objs = {};
    nodes = nodes.reduce((pre, cur) => {
      objs[cur.id] ? "" : (objs[cur.id] = true && pre.push(cur));
      return pre
    }, []);

    node = svgNodes.selectAll('.node').data(nodes, function (d) {
      return d.id;
    });
    var nodeEnter = appendNodeToGraph();
    node = nodeEnter.merge(node);
  }

  /**
   * 创建更新关系和节点
   * @param n
   * @param r
   */
  function updateNodesAndRelationships(n, r) {
    updateRelationships(r);
    updateNodes(n);

    simulation.nodes(nodes);
    simulation.force('link').links(relationships);
  }

  function updateRelationships(r) {
    Array.prototype.push.apply(relationships, r);
    let objs = {};
    relationships = relationships.reduce((pre, cur) => {
      objs[cur.id] ? "" : (objs[cur.id] = true && pre.push(cur));
      return pre
    }, []);

    relationship = svgRelationships.selectAll('.relationship')
      .data(relationships, function (d) {
        return d.id;
      });

    var relationshipEnter = appendRelationshipToGraph();

    relationship = relationshipEnter.relationship.merge(relationship);

    relationshipOutline = svg.selectAll('.relationship .outline');
    relationshipOutline = relationshipEnter.outline.merge(relationshipOutline);

    relationshipOverlay = svg.selectAll('.relationship .overlay');
    relationshipOverlay = relationshipEnter.overlay.merge(relationshipOverlay);

    relationshipText = svg.selectAll('.relationship .text');
    relationshipText = relationshipEnter.text.merge(relationshipText);
  }

  function version() {
    return VERSION;
  }

  function zoomFit(transitionDuration) {
    var bounds = svg.node().getBBox(),
      parent = svg.node().parentElement.parentElement,
      fullWidth = parent.clientWidth,
      fullHeight = parent.clientHeight,
      width = bounds.width,
      height = bounds.height,
      midX = bounds.x + width / 2,
      midY = bounds.y + height / 2;

    if (width === 0 || height === 0) {
      return; // nothing to fit
    }

    svgScale = 0.85 / Math.max(width / fullWidth, height / fullHeight);
    svgTranslate = [fullWidth / 2 - svgScale * midX, fullHeight / 2 - svgScale * midY];

    svg.attr('transform', 'translate(' + svgTranslate[0] + ', ' + svgTranslate[1] + ') scale(' + svgScale + ')');
//        smoothTransform(svgTranslate, svgScale);
  }

  init(_selector, _options);

  return {
    appendRandomDataToNode: appendRandomDataToNode,
    neo4jDataToD3Data: neo4jDataToD3Data,
    size: size,
    updateWithD3Data: updateWithD3Data,
    updateWithNeo4jData: updateWithNeo4jData,
    version: version
  };
}

export {Neo4jD3}
